Support buffering SystemDependent GraphicData

This is a first step to allow buffering of system
dependent data, especially (but not only) for the
system-dependent implementations of graphic output.
For example, for B2DPolygon and Win output, it allows
buffering the Gdiplus::GraphicsPath instead of re-
creating it all the time.
To support that, the change includes forwarding the
current transformation to the renderers in SalGraphics.
The current state in VCL is to transform all and
everything to device coordinates at every single
paint.
I have currently started to do this for ::drawPolyLine
implementations. The fallbacks for all systems will
at the start of that method just transform the data
to device coordinates, so all works as before.
This may also be done for FilledPolygon paint in a later
step, but most urgent is FatLine painting.
An arrangement of shared_ptr/weak_ptr is used so that
either the instance buffering (in the example B2DPolygon)
or the instance managing it can delete it. The instance
managing it currently uses a 1s Timer and a cycle-lifetime
management, but that can be extended in the future
to e.g. include size hints, too.
The mechanism it designed to support multiple Data per
buffering element, e.g. for B2DPolygon at the same time
system-dependent instances of Gdiplus and Cairo can be
buffered, but also PDF-data.
This is achieved semi-automatic by using
typeid(class).hash_code() as key for organization.
The mechanism will be used for now at B2DPolygon, but
is not limited to. There is already a similar but less
general buffer (see GdiPlusBuffer) that can and will
be converted to use this new mechanism.

Added vcl/headless Cairo renderer to support given
ObjectToDevice transformation (not to transform given
B2DPolygon)
Added support for CairoPath buffered at B2DPolygon,
seems to work well. Need to do more tests

Moved usage to templates suggested by Noel Grandin
(Noel Grandin <noelgrandin@gmail.com>), thanks for
these suggestions. Adapted Win usage to that, too.

Converted Win-specific GdiPlus BitmapBuffer to new
mechanism, works well. Checked, the manager holds
now a mix of bitmap and path data under Win

Added a cleanup mechanism to flush all buffered data
at DeInitVCL() using flushAll() at
SystemDependentDataBuffer

Adapted Linux-versions of ::drawPolyLine to support
PixelSnapHairline, for now in a simplified version
that still allows buffering. This will also be used
(and use buffering) for the Cairo-fallback in
X11SalGraphics

Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7
Reviewed-on: https://gerrit.libreoffice.org/59555
Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de>
Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
diff --git a/basegfx/Library_basegfx.mk b/basegfx/Library_basegfx.mk
index 76d06b7..0e42863 100644
--- a/basegfx/Library_basegfx.mk
+++ b/basegfx/Library_basegfx.mk
@@ -72,6 +72,7 @@
    basegfx/source/tools/keystoplerp \
    basegfx/source/tools/numbertools \
    basegfx/source/tools/stringconversiontools \
    basegfx/source/tools/systemdependentdata \
    basegfx/source/tools/tools \
    basegfx/source/tools/unopolypolygon \
    basegfx/source/tools/zoomtools \
diff --git a/basegfx/source/polygon/b2dpolygon.cxx b/basegfx/source/polygon/b2dpolygon.cxx
index 9372cb3..c94f262 100644
--- a/basegfx/source/polygon/b2dpolygon.cxx
+++ b/basegfx/source/polygon/b2dpolygon.cxx
@@ -24,6 +24,7 @@
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/curve/b2dcubicbezier.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/utils/systemdependentdata.hxx>
#include <algorithm>
#include <memory>
#include <vector>
@@ -455,20 +456,21 @@
    }
};

class ImplBufferedData
class ImplBufferedData : public basegfx::SystemDependentDataHolder
{
private:
    // Possibility to hold the last subdivision
    std::unique_ptr< basegfx::B2DPolygon >        mpDefaultSubdivision;
    std::unique_ptr< basegfx::B2DPolygon >  mpDefaultSubdivision;

    // Possibility to hold the last B2DRange calculation
    std::unique_ptr< basegfx::B2DRange >          mpB2DRange;
    std::unique_ptr< basegfx::B2DRange >    mpB2DRange;

public:
    ImplBufferedData()
    :   mpDefaultSubdivision(),
        mpB2DRange()
    {}
    {
    }

    const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const
    {
@@ -1100,6 +1102,26 @@
            maPoints.transform(rMatrix);
        }
    }

    void addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData)
    {
        if(!mpBufferedData)
        {
            mpBufferedData.reset(new ImplBufferedData);
        }

        mpBufferedData->addOrReplaceSystemDependentData(rData);
    }

    basegfx::SystemDependentData_SharedPtr getSystemDependentData(size_t hash_code) const
    {
        if(mpBufferedData)
        {
            return mpBufferedData->getSystemDependentData(hash_code);
        }

        return basegfx::SystemDependentData_SharedPtr();
    }
};

namespace basegfx
@@ -1470,6 +1492,23 @@
        }
    }

    void B2DPolygon::addOrReplaceSystemDependentDataInternal(SystemDependentData_SharedPtr& rData) const
    {
        // Need to get ImplB2DPolygon* from cow_wrapper *without*
        // calling make_unique() here - we do not want to
        // 'modify' the ImplB2DPolygon, but add buffered data that
        // is valid for all referencing instances
        const B2DPolygon* pMe(this);
        const ImplB2DPolygon* pMyImpl(pMe->mpPolygon.get());

        const_cast<ImplB2DPolygon*>(pMyImpl)->addOrReplaceSystemDependentData(rData);
    }

    SystemDependentData_SharedPtr B2DPolygon::getSystemDependantDataInternal(size_t hash_code) const
    {
        return mpPolygon->getSystemDependentData(hash_code);
    }

} // end of namespace basegfx

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/systemdependentdata.cxx b/basegfx/source/tools/systemdependentdata.cxx
new file mode 100755
index 0000000..45f2efb
--- /dev/null
+++ b/basegfx/source/tools/systemdependentdata.cxx
@@ -0,0 +1,141 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <basegfx/utils/systemdependentdata.hxx>

namespace basegfx
{
    SystemDependentDataManager::SystemDependentDataManager()
    {
    }

    SystemDependentDataManager::~SystemDependentDataManager()
    {
    }
} // namespace basegfx

namespace basegfx
{
    MinimalSystemDependentDataManager::MinimalSystemDependentDataManager()
    :   SystemDependentDataManager(),
        maSystemDependentDataReferences()
    {
    }

    MinimalSystemDependentDataManager::~MinimalSystemDependentDataManager()
    {
    }

    void MinimalSystemDependentDataManager::startUsage(basegfx::SystemDependentData_SharedPtr& rData)
    {
        if(rData)
        {
            maSystemDependentDataReferences.insert(rData);
        }
    }

    void MinimalSystemDependentDataManager::endUsage(basegfx::SystemDependentData_SharedPtr& rData)
    {
        if(rData)
        {
            maSystemDependentDataReferences.erase(rData);
        }
    }

    void MinimalSystemDependentDataManager::touchUsage(basegfx::SystemDependentData_SharedPtr& /* rData */)
    {
    }

    void MinimalSystemDependentDataManager::flushAll()
    {
        maSystemDependentDataReferences.clear();
    }
} // namespace basegfx

namespace basegfx
{
    SystemDependentData::SystemDependentData(
        SystemDependentDataManager& rSystemDependentDataManager,
        sal_uInt32 nHoldCycles)
    :   mrSystemDependentDataManager(rSystemDependentDataManager),
        mnHoldCycles(nHoldCycles)
    {
    }

    SystemDependentData::~SystemDependentData()
    {
    }
} // namespace basegfx

namespace basegfx
{
    SystemDependentDataHolder::SystemDependentDataHolder()
    :   maSystemDependentReferences()
    {
    }

    SystemDependentDataHolder::~SystemDependentDataHolder()
    {
        for(auto& candidate : maSystemDependentReferences)
        {
            basegfx::SystemDependentData_SharedPtr aData(candidate.second.lock());

            if(aData)
            {
                aData->getSystemDependentDataManager().endUsage(aData);
            }
        }
    }

    void SystemDependentDataHolder::addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData)
    {
        const size_t hash_code(typeid(*rData.get()).hash_code());
        auto result(maSystemDependentReferences.find(hash_code));

        if(result != maSystemDependentReferences.end())
        {
            basegfx::SystemDependentData_SharedPtr aData(result->second.lock());

            if(aData)
            {
                aData->getSystemDependentDataManager().endUsage(aData);
            }

            maSystemDependentReferences.erase(result);
            result = maSystemDependentReferences.end();
        }

        maSystemDependentReferences[hash_code] = rData;
        rData->getSystemDependentDataManager().startUsage(rData);
    }

    SystemDependentData_SharedPtr SystemDependentDataHolder::getSystemDependentData(size_t hash_code) const
    {
        basegfx::SystemDependentData_SharedPtr aRetval;
        auto result(maSystemDependentReferences.find(hash_code));

        if(result != maSystemDependentReferences.end())
        {
            aRetval = result->second.lock();

            if(aRetval)
            {
                aRetval->getSystemDependentDataManager().touchUsage(aRetval);
            }
            else
            {
                const_cast< SystemDependentDataHolder* >(this)->maSystemDependentReferences.erase(result);
            }
        }

        return aRetval;
    }
} // namespace basegfx

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/cui/source/dialogs/screenshotannotationdlg.cxx b/cui/source/dialogs/screenshotannotationdlg.cxx
index 86e53c8..5805c1f 100644
--- a/cui/source/dialogs/screenshotannotationdlg.cxx
+++ b/cui/source/dialogs/screenshotannotationdlg.cxx
@@ -40,6 +40,7 @@
#include <vcl/vclmedit.hxx>
#include <vcl/button.hxx>
#include <svtools/optionsdrawinglayer.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>

using namespace com::sun::star;

@@ -454,6 +455,7 @@

        // try to use transparency
        if (!mpVirtualBufferDevice->DrawPolyLineDirect(
            basegfx::B2DHomMatrix(),
            aPolygon,
            fLineWidth,
            fTransparency,
diff --git a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx
index e296f39..2350f28 100644
--- a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx
@@ -291,6 +291,9 @@
            maLineAttribute(rLineAttribute),
            maStrokeAttribute(rStrokeAttribute)
        {
            // simplify curve segments: moved here to not need to use it
            // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
            maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon);
        }

        PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(
@@ -301,6 +304,9 @@
            maLineAttribute(rLineAttribute),
            maStrokeAttribute()
        {
            // simplify curve segments: moved here to not need to use it
            // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
            maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon);
        }

        bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
index 0845c33..3295a97 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -127,9 +127,9 @@

        bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect(const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency)
        {
            basegfx::B2DPolygon aLocalPolygon(rSource.getB2DPolygon());
            const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());

            if(!aLocalPolygon.count())
            if(!rLocalPolygon.count())
            {
                // no geometry, done
                return true;
@@ -139,10 +139,14 @@

            mpOutputDevice->SetFillColor();
            mpOutputDevice->SetLineColor(Color(aLineColor));
            aLocalPolygon.transform(maCurrentTransformation);
            //aLocalPolygon.transform(maCurrentTransformation);

            // try drawing; if it did not work, use standard fallback
            return mpOutputDevice->DrawPolyLineDirect( aLocalPolygon, 0.0, fTransparency);
            return mpOutputDevice->DrawPolyLineDirect(
                maCurrentTransformation,
                rLocalPolygon,
                0.0,
                fTransparency);
        }

        bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency)
@@ -158,7 +162,8 @@
            basegfx::B2DPolyPolygon aHairLinePolyPolygon;

            // simplify curve segments
            aLocalPolygon = basegfx::utils::simplifyCurveSegments(aLocalPolygon);
            // moved to PolygonStrokePrimitive2D::PolygonStrokePrimitive2D
            // aLocalPolygon = basegfx::utils::simplifyCurveSegments(aLocalPolygon);

            if(rSource.getStrokeAttribute().isDefault() || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen())
            {
@@ -182,24 +187,24 @@
                return true;
            }

            // check if LineWidth can be simplified in world coordinates
            double fLineWidth(rSource.getLineAttribute().getWidth());

            if(basegfx::fTools::more(fLineWidth, 0.0))
            {
                basegfx::B2DVector aLineWidth(fLineWidth, 0.0);

                aLineWidth = maCurrentTransformation * aLineWidth;
                fLineWidth = aLineWidth.getLength();
            }
                const double fWorldLineWidth(aLineWidth.getLength());

            // draw simple hairline for small line widths
            // see also RenderPolygonStrokePrimitive2D which is used if this try fails
            bool bIsAntiAliasing = getOptionsDrawinglayer().IsAntiAliasing();
            if (   (basegfx::fTools::lessOrEqual(fLineWidth, 1.0) && bIsAntiAliasing)
                || (basegfx::fTools::lessOrEqual(fLineWidth, 1.5) && !bIsAntiAliasing))
            {
                // draw simple hairline
                fLineWidth = 0.0;
                // draw simple hairline for small line widths
                // see also RenderPolygonStrokePrimitive2D which is used if this try fails
                bool bIsAntiAliasing = getOptionsDrawinglayer().IsAntiAliasing();
                if (   (basegfx::fTools::lessOrEqual(fWorldLineWidth, 1.0) && bIsAntiAliasing)
                    || (basegfx::fTools::lessOrEqual(fWorldLineWidth, 1.5) && !bIsAntiAliasing))
                {
                    // draw simple hairline
                    fLineWidth = 0.0;
                }
            }

            const basegfx::BColor aLineColor(
@@ -208,7 +213,9 @@

            mpOutputDevice->SetFillColor();
            mpOutputDevice->SetLineColor(Color(aLineColor));
            aHairLinePolyPolygon.transform(maCurrentTransformation);

            // do not transform self
            // aHairLinePolyPolygon.transform(maCurrentTransformation);

            bool bHasPoints(false);
            bool bTryWorked(false);
@@ -222,6 +229,7 @@
                    bHasPoints = true;

                    if(mpOutputDevice->DrawPolyLineDirect(
                        maCurrentTransformation,
                        aSingle,
                        fLineWidth,
                        fTransparency,
diff --git a/include/basegfx/polygon/b2dpolygon.hxx b/include/basegfx/polygon/b2dpolygon.hxx
index 714d2a1..353e713 100644
--- a/include/basegfx/polygon/b2dpolygon.hxx
+++ b/include/basegfx/polygon/b2dpolygon.hxx
@@ -29,6 +29,7 @@
#include <basegfx/basegfxdllapi.h>

class ImplB2DPolygon;
class SalGraphicsImpl;

namespace basegfx
{
@@ -37,6 +38,9 @@
    class B2DVector;
    class B2DHomMatrix;
    class B2DCubicBezier;
    class SystemDependentData;
    class SystemDependentDataManager;
    typedef std::shared_ptr<SystemDependentData> SystemDependentData_SharedPtr;
}

namespace basegfx
@@ -218,6 +222,26 @@

        /// apply transformation given in matrix form
        void transform(const basegfx::B2DHomMatrix& rMatrix);

        // exclusive management op's for SystemDependentData at B2DPolygon
        template<class T>
        std::shared_ptr<T> getSystemDependentData() const
        {
            return std::static_pointer_cast<T>(getSystemDependantDataInternal(typeid(T).hash_code()));
        }

        template<class T, class... Args>
        std::shared_ptr<T> addOrReplaceSystemDependentData(SystemDependentDataManager& manager, Args&&... args) const
        {
            std::shared_ptr<T> r = std::make_shared<T>(manager, std::forward<Args>(args)...);
            basegfx::SystemDependentData_SharedPtr r2(r);
            addOrReplaceSystemDependentDataInternal(r2);
            return r;
        }

    private:
        void addOrReplaceSystemDependentDataInternal(SystemDependentData_SharedPtr& rData) const;
        SystemDependentData_SharedPtr getSystemDependantDataInternal(size_t hash_code) const;
    };

    // typedef for a vector of B2DPolygons
diff --git a/include/basegfx/utils/systemdependentdata.hxx b/include/basegfx/utils/systemdependentdata.hxx
new file mode 100755
index 0000000..17a0ce4
--- /dev/null
+++ b/include/basegfx/utils/systemdependentdata.hxx
@@ -0,0 +1,139 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#ifndef INCLUDED_BASEGFX_SYSTEMDEPENDENTDATA_HXX
#define INCLUDED_BASEGFX_SYSTEMDEPENDENTDATA_HXX

#include <sal/types.h>
#include <basegfx/basegfxdllapi.h>
#include <memory>
#include <map>
#include <set>

namespace basegfx
{
    class SystemDependentData;
    typedef std::shared_ptr<SystemDependentData> SystemDependentData_SharedPtr;
    typedef std::weak_ptr<SystemDependentData> SystemDependentData_WeakPtr;
} // end of namespace basegfx

namespace basegfx
{
    class BASEGFX_DLLPUBLIC SystemDependentDataManager
    {
    private:
        // noncopyable
        SystemDependentDataManager(const SystemDependentDataManager&) = delete;
        SystemDependentDataManager& operator=(const SystemDependentDataManager&) = delete;

    public:
        SystemDependentDataManager();
        virtual ~SystemDependentDataManager();

        // call from (and with) SystemDependentData objects when start/end/touch
        // usage is needed
        virtual void startUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0;
        virtual void endUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0;
        virtual void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0;

        // flush all buffred data (e.g. cleanup/shutdown)
        virtual void flushAll() = 0;
    };
} // end of namespace basegfx

namespace basegfx
{
    class BASEGFX_DLLPUBLIC MinimalSystemDependentDataManager : public SystemDependentDataManager
    {
    private:
        // example of a minimal SystemDependentDataManager. It *needs to hold*
        // a SystemDependentData_SharedPtr while SystemDependentDataHolder's will
        // use a SystemDependentData_WeakPtr. When the held SystemDependentData_SharedPtr
        // is deleted, the corresponding SystemDependentData_WeakPtr will get void.
        // To make this work, a minimal SystemDependentDataManager *has* to hold at
        // least that one SystemDependentData_SharedPtr.
        // That SystemDependentData_SharedPtr may be (e.g. Timer-based or ressource-based)
        // be freed then. This minimal implementation does never free it, so all stay valid.
        // The instances may still be removed by endUsage calls, but there is no
        // caching/buffering mechanism involved here at all. It's an example, but
        // not used - better use an advanced derivation of SystemDependentDataManager
        std::set< SystemDependentData_SharedPtr >   maSystemDependentDataReferences;

    public:
        MinimalSystemDependentDataManager();
        virtual ~MinimalSystemDependentDataManager() override;

        virtual void startUsage(basegfx::SystemDependentData_SharedPtr& rData) override;
        virtual void endUsage(basegfx::SystemDependentData_SharedPtr& rData) override;
        virtual void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override;
        virtual void flushAll() override;
    };
} // end of namespace basegfx

namespace basegfx
{
    class BASEGFX_DLLPUBLIC SystemDependentData
    {
    private:
        // noncopyable
        SystemDependentData(const SystemDependentData&) = delete;
        SystemDependentData& operator=(const SystemDependentData&) = delete;

        // reference to a SystemDependentDataManager, probably
        // a single, globally used one, but not necessarily
        SystemDependentDataManager&     mrSystemDependentDataManager;

        // number of cycles a SystemDependentDataManager should/might
        // hold this instance - does not have to be used, but should be
        sal_uInt32                      mnHoldCycles;

    public:
        SystemDependentData(
            SystemDependentDataManager& rSystemDependentDataManager,
            sal_uInt32 nHoldCycles = 60);

        // CAUTION! It is VERY important to keep this base class
        // virtual, else typeid(class).hash_code() from derived classes
        // will NOT work what is ESSENTIAL for the SystemDependentData
        // mechanism to work properly. So DO NOT REMOVE virtual here, please.
        virtual ~SystemDependentData();

        // allow access to call startUsage/endUsage/touchUsage
        // using getSystemDependentDataManager()
        SystemDependentDataManager& getSystemDependentDataManager() { return mrSystemDependentDataManager; }

        // number of cycles to hold data
        sal_uInt32 getHoldCycles() const { return mnHoldCycles; }
    };
} // end of namespace basegfx

namespace basegfx
{
    class BASEGFX_DLLPUBLIC SystemDependentDataHolder
    {
    private:
        // Possibility to hold System-Dependent B2DPolygon-Representations
        std::map< size_t, SystemDependentData_WeakPtr > maSystemDependentReferences;

        // noncopyable
        SystemDependentDataHolder(const SystemDependentDataHolder&) = delete;
        SystemDependentDataHolder& operator=(const SystemDependentDataHolder&) = delete;

    public:
        SystemDependentDataHolder();
        virtual ~SystemDependentDataHolder();

        void addOrReplaceSystemDependentData(SystemDependentData_SharedPtr& rData);
        SystemDependentData_SharedPtr getSystemDependentData(size_t hash_code) const;
    };
} // end of namespace basegfx

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx
index 54c22e6..57bedf5 100644
--- a/include/vcl/outdev.hxx
+++ b/include/vcl/outdev.hxx
@@ -800,6 +800,7 @@
    // #i101491#
    // Helper who tries to use SalGDI's DrawPolyLine direct and returns it's bool.
    bool                        DrawPolyLineDirect(
                                    const basegfx::B2DHomMatrix& rObjectTransform,
                                    const basegfx::B2DPolygon& rB2DPolygon,
                                    double fLineWidth = 0.0,
                                    double fTransparency = 0.0,
diff --git a/solenv/clang-format/blacklist b/solenv/clang-format/blacklist
index cd83224..810ca66 100644
--- a/solenv/clang-format/blacklist
+++ b/solenv/clang-format/blacklist
@@ -369,6 +369,7 @@
basegfx/source/tools/keystoplerp.cxx
basegfx/source/tools/numbertools.cxx
basegfx/source/tools/stringconversiontools.cxx
basegfx/source/tools/systemdependentdata.cxx
basegfx/source/tools/tools.cxx
basegfx/source/tools/unopolypolygon.cxx
basegfx/source/tools/unotools.cxx
@@ -5838,6 +5839,7 @@
include/basegfx/utils/keystoplerp.hxx
include/basegfx/utils/lerp.hxx
include/basegfx/utils/rectcliptools.hxx
include/basegfx/utils/systemdependentdata.hxx
include/basegfx/utils/tools.hxx
include/basegfx/utils/unopolypolygon.hxx
include/basegfx/utils/zoomtools.hxx
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx
index f8ada21..fdf2295 100644
--- a/vcl/headless/svpgdi.cxx
+++ b/vcl/headless/svpgdi.cxx
@@ -35,6 +35,8 @@
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/utils/systemdependentdata.hxx>

#if ENABLE_CAIRO_CANVAS
#   if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0)
@@ -720,8 +722,15 @@
        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
    aPoly.setClosed(false);

    drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter,
                 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/);
    drawPolyLine(
        basegfx::B2DHomMatrix(),
        aPoly,
        0.0,
        basegfx::B2DVector(1.0, 1.0),
        basegfx::B2DLineJoin::Miter,
        css::drawing::LineCap_BUTT,
        basegfx::deg2rad(15.0) /*default*/,
        false);
}

void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
@@ -879,18 +888,141 @@
    releaseCairoContext(cr, false, extents);
}

basegfx::B2DRange SvpSalGraphics::drawPolyLine(
    cairo_t* cr,
    const Color& rLineColor,
    bool bAntiAliasB2DDraw,
class SystemDependentData_CairoPath : public basegfx::SystemDependentData
{
private:
    cairo_path_t*       mpCairoPath;

public:
    SystemDependentData_CairoPath(
        basegfx::SystemDependentDataManager& rSystemDependentDataManager,
        cairo_path_t* pCairoPath);
    virtual ~SystemDependentData_CairoPath() override;

    cairo_path_t* getCairoPath() { return mpCairoPath; }
};

SystemDependentData_CairoPath::SystemDependentData_CairoPath(
    basegfx::SystemDependentDataManager& rSystemDependentDataManager,
    cairo_path_t* pCairoPath)
:   basegfx::SystemDependentData(rSystemDependentDataManager),
    mpCairoPath(pCairoPath)
{
}

SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
{
    if(nullptr != mpCairoPath)
    {
        cairo_path_destroy(mpCairoPath);
        mpCairoPath = nullptr;
    }
}

bool SvpSalGraphics::drawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolyLine,
    double fTransparency,
    const basegfx::B2DVector& rLineWidths,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle)
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    const bool bNoJoin = (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(rLineWidths.getX(), 0.0));
    // short circuit if there is nothing to do
    if(0 == rPolyLine.count())
    {
        return true;
    }

    // Wrap call to static verion of ::drawPolyLine by
    // preparing/getting some local data and parameters
    // due to usage in vcl/unx/generic/gdi/salgdi.cxx.
    // This is mainly about extended handling of extents
    // and the way destruction of CairoContext is handled
    // due to current XOR stuff
    cairo_t* cr = getCairoContext(false);
    basegfx::B2DRange aExtents;
    clipRegion(cr);

    bool bRetval(
        drawPolyLine(
            cr,
            &aExtents,
            m_aLineColor,
            getAntiAliasB2DDraw(),
            rObjectToDevice,
            rPolyLine,
            fTransparency,
            rLineWidths,
            eLineJoin,
            eLineCap,
            fMiterMinimumAngle,
            bPixelSnapHairline));

    releaseCairoContext(cr, false, aExtents);

    return bRetval;
}

bool SvpSalGraphics::drawPolyLine(
    cairo_t* cr,
    basegfx::B2DRange* pExtents,
    const Color& rLineColor,
    bool bAntiAliasB2DDraw,
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolyLine,
    double fTransparency,
    const basegfx::B2DVector& rLineWidths,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    // short circuit if there is nothing to do
    if(0 == rPolyLine.count())
    {
        return true;
    }

    // need to check/handle LineWidth when ObjectToDevice transformation is used
    basegfx::B2DVector aLineWidths(rLineWidths);
    const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
    const basegfx::B2DVector aDeviceLineWidths(bObjectToDeviceIsIdentity ? rLineWidths : rObjectToDevice * rLineWidths);
    const bool bCorrectLineWidth(!bObjectToDeviceIsIdentity && aDeviceLineWidths.getX() < 1.0 && aLineWidths.getX() >= 1.0);

    // on-demand inverse of ObjectToDevice transformation
    basegfx::B2DHomMatrix aObjectToDeviceInv;

    if(bCorrectLineWidth)
    {
        if(aObjectToDeviceInv.isIdentity())
        {
            aObjectToDeviceInv = rObjectToDevice;
            aObjectToDeviceInv.invert();
        }

        // calculate-back logical LineWidth for a hairline
        aLineWidths = aObjectToDeviceInv * basegfx::B2DVector(1.0, 1.0);
    }

    if(!bObjectToDeviceIsIdentity)
    {
        // set ObjectToDevice transformation
        cairo_matrix_t aMatrix;

        cairo_matrix_init(
            &aMatrix,
            rObjectToDevice.get( 0, 0 ),
            rObjectToDevice.get( 1, 0 ),
            rObjectToDevice.get( 0, 1 ),
            rObjectToDevice.get( 1, 1 ),
            rObjectToDevice.get( 0, 2 ),
            rObjectToDevice.get( 1, 2 ));
        cairo_set_matrix(cr, &aMatrix);
    }

    const bool bNoJoin((basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(aLineWidths.getX(), 0.0)));

    // setup line attributes
    cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
@@ -933,76 +1065,131 @@
        }
    }

    cairo_set_source_rgba(cr, rLineColor.GetRed()/255.0,
                              rLineColor.GetGreen()/255.0,
                              rLineColor.GetBlue()/255.0,
                              1.0-fTransparency);
    cairo_set_source_rgba(
        cr,
        rLineColor.GetRed()/255.0,
        rLineColor.GetGreen()/255.0,
        rLineColor.GetBlue()/255.0,
        1.0-fTransparency);

    cairo_set_line_join(cr, eCairoLineJoin);
    cairo_set_line_cap(cr, eCairoLineCap);
    cairo_set_line_width(cr, rLineWidths.getX());
    cairo_set_line_width(cr, aLineWidths.getX());
    cairo_set_miter_limit(cr, fMiterLimit);

    // try to access buffered data
    std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
        rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>());

    basegfx::B2DRange extents(0, 0, 0, 0);

    if (!bNoJoin)
    if(pSystemDependentData_CairoPath)
    {
        AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !bAntiAliasB2DDraw, true);
        extents = getClippedStrokeDamage(cr);
        cairo_stroke(cr);
        // check data validity
        if(nullptr == pSystemDependentData_CairoPath->getCairoPath())
        {
            // data invalid, forget
            pSystemDependentData_CairoPath.reset();
        }
    }

    if(pSystemDependentData_CairoPath)
    {
        // re-use data
        cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
    }
    else
    {
        const int nPointCount = rPolyLine.count();
        // emulate rendering::PathJoinType::NONE by painting single edges
        const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
        basegfx::B2DPolygon aEdge;
        aEdge.append(rPolyLine.getB2DPoint(0));
        aEdge.append(basegfx::B2DPoint(0.0, 0.0));
        // create data
        basegfx::B2DPolygon aPolyLine(rPolyLine);

        for (sal_uInt32 i = 0; i < nEdgeCount; ++i)
        if(bPixelSnapHairline)
        {
            const sal_uInt32 nNextIndex((i + 1) % nPointCount);
            aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
            aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i % nPointCount));
            aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));
            // Need to take care of PixelSnapHairline now. The 'short' version
            // will manipulate the Polygon by using the known tooling at
            // basegfx. To do this correct, this needs to be done in device
            // coordinates, so when the transformation is used, transform
            // to device first, execute, transform back using the inverse.
            // The important part for buffering the result and not need to
            // do this at each repaint (for now) is to change a copy of the
            // Polygon to create the CairoData, but to buffer it at the original
            // unmodified Polygon.
            // The 'long' version would be to add this to AddPolygonToPath
            // equal as done in Win version (see impPixelSnap), should be done
            // later
            if(!bObjectToDeviceIsIdentity)
            {
                aPolyLine.transform(rObjectToDevice);
            }

            AddPolygonToPath(cr, aEdge, false, !bAntiAliasB2DDraw, true);
            aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);

            extents.expand(getStrokeDamage(cr));
            if(!bObjectToDeviceIsIdentity)
            {
                if(aObjectToDeviceInv.isIdentity())
                {
                    aObjectToDeviceInv = rObjectToDevice;
                    aObjectToDeviceInv.invert();
                }

            cairo_stroke(cr);

            // prepare next step
            aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
                aPolyLine.transform(aObjectToDeviceInv);
            }
        }

        extents.intersect(getClipBox(cr));
        if (!bNoJoin)
        {
            AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !bAntiAliasB2DDraw, true);
        }
        else
        {
            const sal_uInt32 nPointCount(rPolyLine.count());
            const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
            basegfx::B2DPolygon aEdge;

            aEdge.append(rPolyLine.getB2DPoint(0));
            aEdge.append(basegfx::B2DPoint(0.0, 0.0));

            for (sal_uInt32 i(0); i < nEdgeCount; i++)
            {
                const sal_uInt32 nNextIndex((i + 1) % nPointCount);
                aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
                aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i));
                aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));

                AddPolygonToPath(cr, aEdge, false, !bAntiAliasB2DDraw, true);

                // prepare next step
                aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
            }
        }

        // copy and add to buffering mechanism
        rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
            SalGraphics::getSystemDependentDataManager(),
            cairo_copy_path(cr));
    }

    return extents;
}
    // extract extents
    if(nullptr != pExtents)
    {
        *pExtents = getClippedStrokeDamage(cr);
    }

bool SvpSalGraphics::drawPolyLine(
    const basegfx::B2DPolygon& rPolyLine,
    double fTransparency,
    const basegfx::B2DVector& rLineWidths,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle)
{
    // short circuit if there is nothing to do
    if (rPolyLine.count() <= 0)
        return true;
    // draw and consume
    cairo_stroke(cr);

    cairo_t* cr = getCairoContext(false);
    clipRegion(cr);
    if(!bObjectToDeviceIsIdentity)
    {
        // reset ObjectToDevice transformation if was set (safe, may
        // be better suited at ::getCairoContext)
        cairo_identity_matrix(cr);
    }

    basegfx::B2DRange extents = drawPolyLine(cr, m_aLineColor, getAntiAliasB2DDraw(), rPolyLine,
                                             fTransparency, rLineWidths, eLineJoin, eLineCap, fMiterMinimumAngle);

    releaseCairoContext(cr, false, extents);
    if(nullptr != pExtents && !pExtents->isEmpty() && !bObjectToDeviceIsIdentity)
    {
        // transform extents to DeviceCoordiinates if used. These
        // were calculated with ObjectToDevice transformation actively set,
        // but use DeviceCoordinates locally
        pExtents->transform(rObjectToDevice);
    }

    return true;
}
diff --git a/vcl/inc/headless/svpgdi.hxx b/vcl/inc/headless/svpgdi.hxx
index 8877b2e..158c331 100644
--- a/vcl/inc/headless/svpgdi.hxx
+++ b/vcl/inc/headless/svpgdi.hxx
@@ -99,16 +99,23 @@
    static cairo_user_data_key_t* getDamageKey();

    static void clipRegion(cairo_t* cr, const vcl::Region& rClipRegion);
    static basegfx::B2DRange drawPolyLine(

    // need this static version of ::drawPolyLine for usage from
    // vcl/unx/generic/gdi/salgdi.cxx. It gets wrapped by
    // ::drawPolyLine with some added parameters (see there)
    static bool drawPolyLine(
        cairo_t* cr,
        basegfx::B2DRange* pExtents,
        const Color& rLineColor,
        bool bAntiAliasB2DDraw,
        const basegfx::B2DHomMatrix& rObjectToDevice,
        const basegfx::B2DPolygon& rPolyLine,
        double fTransparency,
        const basegfx::B2DVector& rLineWidths,
        basegfx::B2DLineJoin eLineJoin,
        css::drawing::LineCap eLineCap,
        double fMiterMinimumAngle);
        double fMiterMinimumAngle,
        bool bPixelSnapHairline);

private:
    void invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags);
@@ -194,12 +201,15 @@
    virtual void            drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
    virtual void            drawRect( long nX, long nY, long nWidth, long nHeight ) override;
    virtual bool            drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;
    virtual bool            drawPolyLine( const basegfx::B2DPolygon&,
                                          double fTransparency,
                                          const basegfx::B2DVector& rLineWidths,
                                          basegfx::B2DLineJoin,
                                          css::drawing::LineCap,
                                          double fMiterMinimumAngle) override;
    virtual bool            drawPolyLine(
                                const basegfx::B2DHomMatrix& rObjectToDevice,
                                const basegfx::B2DPolygon&,
                                double fTransparency,
                                const basegfx::B2DVector& rLineWidths,
                                basegfx::B2DLineJoin,
                                css::drawing::LineCap,
                                double fMiterMinimumAngle,
                                bool bPixelSnapHairline) override;
    virtual void            drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
    virtual void            drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
    virtual void            drawPolyPolygon( sal_uInt32 nPoly,
diff --git a/vcl/inc/openglgdiimpl.hxx b/vcl/inc/openglgdiimpl.hxx
index 952a91e..f2fd9b7 100644
--- a/vcl/inc/openglgdiimpl.hxx
+++ b/vcl/inc/openglgdiimpl.hxx
@@ -252,12 +252,14 @@
    virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;

    virtual bool drawPolyLine(
                const basegfx::B2DHomMatrix& rObjectToDevice,
                const basegfx::B2DPolygon&,
                double fTransparency,
                const basegfx::B2DVector& rLineWidths,
                basegfx::B2DLineJoin,
                css::drawing::LineCap,
                double fMiterMinimumAngle) override;
                double fMiterMinimumAngle,
                bool bPixelSnapHairline) override;

    virtual bool drawPolyLineBezier(
                sal_uInt32 nPoints,
diff --git a/vcl/inc/qt5/Qt5Graphics.hxx b/vcl/inc/qt5/Qt5Graphics.hxx
index fa9823b..6bde41f 100644
--- a/vcl/inc/qt5/Qt5Graphics.hxx
+++ b/vcl/inc/qt5/Qt5Graphics.hxx
@@ -116,9 +116,11 @@
    virtual bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
                                       const SalPoint* const* pPtAry,
                                       const PolyFlags* const* pFlgAry) override;
    virtual bool drawPolyLine(const basegfx::B2DPolygon&, double fTransparency,
    virtual bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
                              const basegfx::B2DPolygon&, double fTransparency,
                              const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin,
                              css::drawing::LineCap eLineCap, double fMiterMinimumAngle) override;
                              css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
                              bool bPixelSnapHairline) override;
    virtual bool drawGradient(const tools::PolyPolygon&, const Gradient&) override;

    virtual void copyArea(long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
diff --git a/vcl/inc/quartz/salgdi.h b/vcl/inc/quartz/salgdi.h
index 31a8353a..b03fa5b 100644
--- a/vcl/inc/quartz/salgdi.h
+++ b/vcl/inc/quartz/salgdi.h
@@ -229,12 +229,14 @@
    virtual bool            drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) override;
    virtual bool            drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry ) override;
    virtual bool            drawPolyLine(
                                const basegfx::B2DHomMatrix& rObjectToDevice,
                                const basegfx::B2DPolygon&,
                                double fTransparency,
                                const basegfx::B2DVector& rLineWidths,
                                basegfx::B2DLineJoin,
                                css::drawing::LineCap eLineCap,
                                double fMiterMinimumAngle) override;
                                double fMiterMinimumAngle,
                                bool bPixelSnapHairline) override;
    virtual bool            drawGradient( const tools::PolyPolygon&, const Gradient& ) override { return false; };

    // CopyArea --> No RasterOp, but ClipRegion
diff --git a/vcl/inc/salgdi.hxx b/vcl/inc/salgdi.hxx
index 8744c8f..866a10b 100644
--- a/vcl/inc/salgdi.hxx
+++ b/vcl/inc/salgdi.hxx
@@ -54,6 +54,7 @@
    class B2DVector;
    class B2DPolygon;
    class B2DPolyPolygon;
    class SystemDependentDataManager;
}

typedef sal_Unicode sal_Ucs; // TODO: use sal_UCS4 instead of sal_Unicode
@@ -76,6 +77,10 @@

    virtual SalGraphicsImpl*    GetImpl() const = 0;

    // access to single global managing instance of a basegfx::SystemDependentDataManager,
    // used to handle graphic data in system-dependent form
    static basegfx::SystemDependentDataManager& getSystemDependentDataManager();

    /// Check that our mpImpl is OpenGL and return the context, otherwise NULL.
    rtl::Reference<OpenGLContext> GetOpenGLContext() const;

@@ -242,12 +247,14 @@
                                    const OutputDevice *i_pOutDev);

    bool                        DrawPolyLine(
                                    const basegfx::B2DHomMatrix& rObjectToDevice,
                                    const basegfx::B2DPolygon& i_rPolygon,
                                    double i_fTransparency,
                                    const basegfx::B2DVector& i_rLineWidth,
                                    basegfx::B2DLineJoin i_eLineJoin,
                                    css::drawing::LineCap i_eLineCap,
                                    double i_fMiterMinimumAngle,
                                    bool bPixelSnapHairline,
                                    const OutputDevice* i_pOutDev);

    bool                        DrawPolyLineBezier(
@@ -458,12 +465,14 @@
    virtual bool                drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) = 0;

    virtual bool                drawPolyLine(
                                    const basegfx::B2DHomMatrix& rObjectToDevice,
                                    const basegfx::B2DPolygon&,
                                    double fTransparency,
                                    const basegfx::B2DVector& rLineWidths,
                                    basegfx::B2DLineJoin,
                                    css::drawing::LineCap,
                                    double fMiterMinimumAngle) = 0;
                                    double fMiterMinimumAngle,
                                    bool bPixelSnapHairline) = 0;

    virtual bool                drawPolyLineBezier(
                                    sal_uInt32 nPoints,
diff --git a/vcl/inc/salgdiimpl.hxx b/vcl/inc/salgdiimpl.hxx
index 3fb3a0b..8e545d0 100644
--- a/vcl/inc/salgdiimpl.hxx
+++ b/vcl/inc/salgdiimpl.hxx
@@ -103,12 +103,14 @@
    virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) = 0;

    virtual bool drawPolyLine(
                const basegfx::B2DHomMatrix& rObjectToDevice,
                const basegfx::B2DPolygon&,
                double fTransparency,
                const basegfx::B2DVector& rLineWidths,
                basegfx::B2DLineJoin,
                css::drawing::LineCap,
                double fMiterMinimumAngle) = 0;
                double fMiterMinimumAngle,
                bool bPixelSnapHairline) = 0;

    virtual bool drawPolyLineBezier(
                sal_uInt32 nPoints,
diff --git a/vcl/inc/unx/genpspgraphics.h b/vcl/inc/unx/genpspgraphics.h
index fd06248..3f6bae6 100644
--- a/vcl/inc/unx/genpspgraphics.h
+++ b/vcl/inc/unx/genpspgraphics.h
@@ -128,12 +128,15 @@
                                             PCONSTSALPOINT* pPtAry ) override;
    virtual bool            drawPolyPolygon( const basegfx::B2DPolyPolygon&,
                                             double fTransparency ) override;
    virtual bool            drawPolyLine( const basegfx::B2DPolygon&,
                                          double fTransparency,
                                          const basegfx::B2DVector& rLineWidths,
                                          basegfx::B2DLineJoin,
                                          css::drawing::LineCap,
                                          double fMiterMinimumAngle) override;
    virtual bool            drawPolyLine(
                                const basegfx::B2DHomMatrix& rObjectToDevice,
                                const basegfx::B2DPolygon&,
                                double fTransparency,
                                const basegfx::B2DVector& rLineWidths,
                                basegfx::B2DLineJoin,
                                css::drawing::LineCap,
                                double fMiterMinimumAngle,
                                bool bPixelSnapHairline) override;
    virtual bool            drawPolyLineBezier( sal_uInt32 nPoints,
                                                const SalPoint* pPtAry,
                                                const PolyFlags* pFlgAry ) override;
diff --git a/vcl/inc/unx/salgdi.h b/vcl/inc/unx/salgdi.h
index 408434f..8b73c45 100644
--- a/vcl/inc/unx/salgdi.h
+++ b/vcl/inc/unx/salgdi.h
@@ -163,12 +163,14 @@
    virtual bool                    drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;

    virtual bool                    drawPolyLine(
                                        const basegfx::B2DHomMatrix& rObjectToDevice,
                                        const basegfx::B2DPolygon&,
                                        double fTransparency,
                                        const basegfx::B2DVector& rLineWidth,
                                        basegfx::B2DLineJoin,
                                        css::drawing::LineCap,
                                        double fMiterMinimumAngle) override;
                                        double fMiterMinimumAngle,
                                        bool bPixelSnapHairline) override;

    virtual bool                    drawGradient( const tools::PolyPolygon&, const Gradient& ) override;

diff --git a/vcl/inc/win/salbmp.h b/vcl/inc/win/salbmp.h
index f139a86..7f172d2 100644
--- a/vcl/inc/win/salbmp.h
+++ b/vcl/inc/win/salbmp.h
@@ -23,6 +23,7 @@
#include <tools/gen.hxx>
#include <win/wincomp.hxx>
#include <salbmp.hxx>
#include <basegfx/utils/systemdependentdata.hxx>
#include <memory>


@@ -32,26 +33,13 @@
class   SalGraphics;
namespace Gdiplus { class Bitmap; }

class WinSalBitmap : public SalBitmap
class WinSalBitmap : public SalBitmap, public basegfx::SystemDependentDataHolder
{
private:
    friend class GdiPlusBuffer; // allow buffer to remove maGdiPlusBitmap and mpAssociatedAlpha eventually

    Size                maSize;
    HGLOBAL             mhDIB;
    HBITMAP             mhDDB;

    // the buffered evtl. used Gdiplus::Bitmap instance. It is managed by
    // GdiPlusBuffer. To make this safe, it is only handed out as shared
    // pointer; the GdiPlusBuffer may delete the local instance.

    // mpAssociatedAlpha holds the last WinSalBitmap used to construct an
    // evtl. buffered GdiPlusBmp. This is needed since the GdiPlusBmp is a single
    // instance and remembered only on the content-WinSalBitmap, not on the
    // alpha-WinSalBitmap.
    std::shared_ptr< Gdiplus::Bitmap >       maGdiPlusBitmap;
    const WinSalBitmap* mpAssociatedAlpha;

    sal_uInt16          mnBitCount;

    Gdiplus::Bitmap*    ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource);
@@ -98,6 +86,22 @@
    virtual bool                ScalingSupported() const override;
    virtual bool                Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) override;
    virtual bool                Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol ) override;

    // exclusive management op's for SystemDependentData at WinSalBitmap
    template<class T>
    std::shared_ptr<T> getSystemDependentData() const
    {
        return std::static_pointer_cast<T>(basegfx::SystemDependentDataHolder::getSystemDependentData(typeid(T).hash_code()));
    }

    template<class T, class... Args>
    std::shared_ptr<T> addOrReplaceSystemDependentData(basegfx::SystemDependentDataManager& manager, Args&&... args) const
    {
        std::shared_ptr<T> r = std::make_shared<T>(manager, std::forward<Args>(args)...);
        basegfx::SystemDependentData_SharedPtr r2(r);
        const_cast< WinSalBitmap* >(this)->basegfx::SystemDependentDataHolder::addOrReplaceSystemDependentData(r2);
        return r;
    }
};

#endif // INCLUDED_VCL_INC_WIN_SALBMP_H
diff --git a/vcl/inc/win/salgdi.h b/vcl/inc/win/salgdi.h
index 0540b66..51d14cb 100644
--- a/vcl/inc/win/salgdi.h
+++ b/vcl/inc/win/salgdi.h
@@ -237,12 +237,14 @@
    virtual void        drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override;
    virtual bool        drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;
    virtual bool        drawPolyLine(
        const basegfx::B2DHomMatrix& rObjectToDevice,
        const basegfx::B2DPolygon&,
        double fTransparency,
        const basegfx::B2DVector& rLineWidth,
        basegfx::B2DLineJoin,
        css::drawing::LineCap,
        double fMiterMinimumAngle) override;
        double fMiterMinimumAngle,
        bool bPixelSnapHairline) override;
    virtual bool        drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) override;
    virtual bool        drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) override;
    virtual bool        drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry ) override;
diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx
index 5efecae..9fff1d5 100644
--- a/vcl/opengl/gdiimpl.cxx
+++ b/vcl/opengl/gdiimpl.cxx
@@ -1549,8 +1549,15 @@
        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
    aPoly.setClosed(false);

    drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter,
                 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/);
    drawPolyLine(
        basegfx::B2DHomMatrix(),
        aPoly,
        0.0,
        basegfx::B2DVector(1.0, 1.0),
        basegfx::B2DLineJoin::Miter,
        css::drawing::LineCap_BUTT,
        basegfx::deg2rad(15.0) /*default*/,
        false);
}

void OpenGLSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
@@ -1594,19 +1601,39 @@
    return true;
}

bool OpenGLSalGraphicsImpl::drawPolyLine(const basegfx::B2DPolygon& rPolygon, double fTransparency,
                                         const basegfx::B2DVector& rLineWidth, basegfx::B2DLineJoin eLineJoin,
                                         css::drawing::LineCap eLineCap, double fMiterMinimumAngle)
bool OpenGLSalGraphicsImpl::drawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolygon,
    double fTransparency,
    const basegfx::B2DVector& rLineWidth,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    VCL_GL_INFO("::drawPolyLine " << rPolygon.getB2DRange());

    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
    basegfx::B2DPolygon aPolyLine(rPolygon);
    aPolyLine.transform(rObjectToDevice);
    if(bPixelSnapHairline) { aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); }
    const basegfx::B2DVector aLineWidth(rObjectToDevice * rLineWidth);

    // addDrawPolyLine() assumes that there are no duplicate points in the
    // polygon.
    basegfx::B2DPolygon aPolygon(rPolygon);
    aPolygon.removeDoublePoints();
    // basegfx::B2DPolygon aPolygon(rPolygon);
    aPolyLine.removeDoublePoints();

    mpRenderList->addDrawPolyLine(aPolygon, fTransparency, rLineWidth, eLineJoin, eLineCap,
                                  fMiterMinimumAngle, mnLineColor, mrParent.getAntiAliasB2DDraw());
    mpRenderList->addDrawPolyLine(
        aPolyLine,
        fTransparency,
        aLineWidth,
        eLineJoin,
        eLineCap,
        fMiterMinimumAngle,
        mnLineColor,
        mrParent.getAntiAliasB2DDraw());

    PostBatchDraw();
    return true;
}
diff --git a/vcl/qt5/Qt5Graphics_GDI.cxx b/vcl/qt5/Qt5Graphics_GDI.cxx
index 1a61fe5..5470295c 100644
--- a/vcl/qt5/Qt5Graphics_GDI.cxx
+++ b/vcl/qt5/Qt5Graphics_GDI.cxx
@@ -316,28 +316,39 @@
    return false;
}

bool Qt5Graphics::drawPolyLine(const basegfx::B2DPolygon& rPolyLine, double fTransparency,
bool Qt5Graphics::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
                               const basegfx::B2DPolygon& rPolyLine, double fTransparency,
                               const basegfx::B2DVector& rLineWidths,
                               basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
                               double fMiterMinimumAngle)
                               double fMiterMinimumAngle, bool bPixelSnapHairline)
{
    if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
        return true;

    // short circuit if there is nothing to do
    const int nPointCount = rPolyLine.count();
    if (nPointCount <= 0)
    if (0 == rPolyLine.count())
    {
        return true;
    }

    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
    basegfx::B2DPolygon aPolyLine(rPolyLine);
    aPolyLine.transform(rObjectToDevice);
    if (bPixelSnapHairline)
    {
        aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
    }
    const basegfx::B2DVector aLineWidths(rObjectToDevice * rLineWidths);

    // setup poly-polygon path
    QPainterPath aPath;
    AddPolygonToPath(aPath, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true);
    AddPolygonToPath(aPath, aPolyLine, aPolyLine.isClosed(), !getAntiAliasB2DDraw(), true);

    Qt5Painter aPainter(*this, false, 255 * (1.0 - fTransparency));

    // setup line attributes
    QPen aPen = aPainter.pen();
    aPen.setWidth(rLineWidths.getX());
    aPen.setWidth(aLineWidths.getX());

    switch (eLineJoin)
    {
diff --git a/vcl/quartz/salgdicommon.cxx b/vcl/quartz/salgdicommon.cxx
index 77241c2..09adf78 100644
--- a/vcl/quartz/salgdicommon.cxx
+++ b/vcl/quartz/salgdicommon.cxx
@@ -24,6 +24,7 @@
#include <cstring>

#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <osl/endian.h>
#include <osl/file.hxx>
#include <sal/types.h>
@@ -943,18 +944,20 @@
    ImplDrawPixel( nX, nY, aPixelColor );
}

bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine,
                                    double fTransparency,
                                    const basegfx::B2DVector& rLineWidths,
                                    basegfx::B2DLineJoin eLineJoin,
                                    css::drawing::LineCap eLineCap,
                                    double fMiterMinimumAngle)
bool AquaSalGraphics::drawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolyLine,
    double fTransparency,
    const basegfx::B2DVector& rLineWidths,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    DBG_DRAW_OPERATION("drawPolyLine", true);

    // short circuit if there is nothing to do
    const int nPointCount = rPolyLine.count();
    if( nPointCount <= 0 )
    if(0 == rPolyLine.count())
    {
        DBG_DRAW_OPERATION_EXIT_EARLY("drawPolyLine");
        return true;
@@ -968,16 +971,23 @@
    }
#endif

    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
    const basegfx::B2DVector aLineWidths(rObjectToDevice * rLineWidths);

    // #i101491# Aqua does not support B2DLineJoin::NONE; return false to use
    // the fallback (own geometry preparation)
    // #i104886# linejoin-mode and thus the above only applies to "fat" lines
    if( (basegfx::B2DLineJoin::NONE == eLineJoin) &&
        (rLineWidths.getX() > 1.3) )
    if( (basegfx::B2DLineJoin::NONE == eLineJoin) && (aLineWidths.getX() > 1.3) )
    {
        DBG_DRAW_OPERATION_EXIT_EARLY("drawPolyLine");
        return false;
    }

    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
    basegfx::B2DPolygon aPolyLine(rPolyLine);
    aPolyLine.transform(rObjectToDevice);
    if(bPixelSnapHairline) { aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); }

    // setup line attributes
    CGLineJoin aCGLineJoin = kCGLineJoinMiter;
    switch( eLineJoin )
@@ -1014,7 +1024,12 @@
    // setup poly-polygon path
    CGMutablePathRef xPath = CGPathCreateMutable();
    SAL_INFO( "vcl.cg", "CGPathCreateMutable() = " << xPath );
    AddPolygonToPath( xPath, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true );
    AddPolygonToPath(
        xPath,
        aPolyLine,
        aPolyLine.isClosed(),
        !getAntiAliasB2DDraw(),
        true);

    const CGRect aRefreshRect = CGPathGetBoundingBox( xPath );
    SAL_INFO( "vcl.cg", "CGPathGetBoundingBox(" << xPath << ") = " << aRefreshRect );
@@ -1034,7 +1049,7 @@
        CGContextSetAlpha( mrContext, 1.0 - fTransparency );
        CGContextSetLineJoin( mrContext, aCGLineJoin );
        CGContextSetLineCap( mrContext, aCGLineCap );
        CGContextSetLineWidth( mrContext, rLineWidths.getX() );
        CGContextSetLineWidth( mrContext, aLineWidths.getX() );
        CGContextSetMiterLimit(mrContext, fCGMiterLimit);
        SAL_INFO( "vcl.cg", "CGContextDrawPath(" << mrContext << ",kCGPathStroke)" );
        CGContextDrawPath( mrContext, kCGPathStroke );
diff --git a/vcl/source/app/svmain.cxx b/vcl/source/app/svmain.cxx
index a91b5c6..9e4e3ca 100644
--- a/vcl/source/app/svmain.cxx
+++ b/vcl/source/app/svmain.cxx
@@ -101,6 +101,8 @@
#include <opengl/zone.hxx>
#include <opengl/watchdog.hxx>

#include <basegfx/utils/systemdependentdata.hxx>

#if OSL_DEBUG_LEVEL > 0
#include <typeinfo>
#include <rtl/strbuf.hxx>
@@ -426,6 +428,9 @@
    }
    ImplSVData* pSVData = ImplGetSVData();

    // cleanup SystemDependentData
    SalGraphics::getSystemDependentDataManager().flushAll();

    // lp#1560328: clear cache before disposing rest of VCL
    if(pSVData->mpBlendFrameCache)
        pSVData->mpBlendFrameCache->m_aLastResult.Clear();
diff --git a/vcl/source/gdi/salgdilayout.cxx b/vcl/source/gdi/salgdilayout.cxx
index 8a70dee..08fb2fa 100644
--- a/vcl/source/gdi/salgdilayout.cxx
+++ b/vcl/source/gdi/salgdilayout.cxx
@@ -31,6 +31,10 @@
#include <salgdi.hxx>
#include <salframe.hxx>
#include <basegfx/numeric/ftools.hxx> //for F_PI180
#include <basegfx/utils/systemdependentdata.hxx>
#include <cppuhelper/basemutex.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <o3tl/make_unique.hxx>

// The only common SalFrame method

@@ -75,6 +79,131 @@
    }
}

basegfx::SystemDependentDataManager& SalGraphics::getSystemDependentDataManager()
{
    typedef ::std::map< basegfx::SystemDependentData_SharedPtr, sal_uInt32 > EntryMap;

    class SystemDependentDataBuffer : public basegfx::SystemDependentDataManager, protected cppu::BaseMutex, public Timer
    {
    private:
        EntryMap        maEntries;

    public:
        SystemDependentDataBuffer( const sal_Char *pDebugName )
        :   basegfx::SystemDependentDataManager(),
            Timer( pDebugName ),
            maEntries()
        {
            SetTimeout(1000);
            SetStatic();
        }

        virtual ~SystemDependentDataBuffer() override
        {
            Stop();
        }

        void startUsage(basegfx::SystemDependentData_SharedPtr& rData) override
        {
            ::osl::MutexGuard aGuard(m_aMutex);
            EntryMap::iterator aFound(maEntries.find(rData));

            if(aFound == maEntries.end())
            {
                if(maEntries.empty())
                {
                    Start();
                }

                maEntries[rData] = rData->getHoldCycles();
            }
        }

        void endUsage(basegfx::SystemDependentData_SharedPtr& rData) override
        {
            ::osl::MutexGuard aGuard(m_aMutex);
            EntryMap::iterator aFound(maEntries.find(rData));

            if(aFound != maEntries.end())
            {
                maEntries.erase(aFound);

                if(maEntries.empty())
                {
                    Stop();
                }
            }
        }

        void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override
        {
            ::osl::MutexGuard aGuard(m_aMutex);
            EntryMap::iterator aFound(maEntries.find(rData));

            if(aFound != maEntries.end())
            {
                aFound->second = rData->getHoldCycles();
            }
        }

        void flushAll() override
        {
            ::osl::MutexGuard aGuard(m_aMutex);
            EntryMap::iterator aIter(maEntries.begin());

            Stop();

            while(aIter != maEntries.end())
            {
                EntryMap::iterator aDelete(aIter);
                ++aIter;
                maEntries.erase(aDelete);
            }
        }

        // from parent Timer
        virtual void Invoke() override
        {
            ::osl::MutexGuard aGuard(m_aMutex);
            EntryMap::iterator aIter(maEntries.begin());

            while(aIter != maEntries.end())
            {
                if(aIter->second)
                {
                    aIter->second--;
                    ++aIter;
                }
                else
                {
                    EntryMap::iterator aDelete(aIter);
                    ++aIter;
                    maEntries.erase(aDelete);

                    if(maEntries.empty())
                    {
                        Stop();
                    }
                }
            }

            if(!maEntries.empty())
            {
                Start();
            }
        }
    };

    static std::unique_ptr<SystemDependentDataBuffer> aSystemDependentDataBuffer;

    if(!aSystemDependentDataBuffer)
    {
        aSystemDependentDataBuffer = o3tl::make_unique<SystemDependentDataBuffer>(nullptr);
    }

    return *aSystemDependentDataBuffer.get();
}

rtl::Reference<OpenGLContext> SalGraphics::GetOpenGLContext() const
{
    OpenGLSalGraphicsImpl *pImpl = dynamic_cast<OpenGLSalGraphicsImpl*>(GetImpl());
@@ -512,22 +641,56 @@
    return bRet;
}

bool SalGraphics::DrawPolyLine( const basegfx::B2DPolygon& i_rPolygon,
                                double i_fTransparency,
                                const basegfx::B2DVector& i_rLineWidth,
                                basegfx::B2DLineJoin i_eLineJoin,
                                css::drawing::LineCap i_eLineCap,
                                double i_fMiterMinimumAngle,
                                const OutputDevice* i_pOutDev )
bool SalGraphics::DrawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& i_rPolygon,
    double i_fTransparency,
    const basegfx::B2DVector& i_rLineWidth,
    basegfx::B2DLineJoin i_eLineJoin,
    css::drawing::LineCap i_eLineCap,
    double i_fMiterMinimumAngle,
    bool bPixelSnapHairline,
    const OutputDevice* i_pOutDev)
{
    bool bRet = false;

    if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) )
    {
        basegfx::B2DPolygon aMirror( mirror( i_rPolygon, i_pOutDev ) );
        bRet = drawPolyLine( aMirror, i_fTransparency, i_rLineWidth, i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle );
        // if mirrored, we need to apply transformation since it is
        // not clear what 'mirror' does - might be changed when this
        // happens often
        basegfx::B2DPolygon aMirror(i_rPolygon);

        aMirror.transform(rObjectToDevice);
        aMirror = mirror(aMirror, i_pOutDev);
        // basegfx::B2DPolygon aMirror( mirror( i_rPolygon, i_pOutDev ) );

        // also need to transform LineWidth
        const basegfx::B2DVector aLineWidth(rObjectToDevice * i_rLineWidth);

        bRet = drawPolyLine(
            basegfx::B2DHomMatrix(), // now empty transformation, already used
            aMirror,
            i_fTransparency,
            aLineWidth,
            i_eLineJoin,
            i_eLineCap,
            i_fMiterMinimumAngle,
            bPixelSnapHairline);
    }
    else
        bRet = drawPolyLine( i_rPolygon, i_fTransparency, i_rLineWidth, i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle );
    {
        bRet = drawPolyLine(
            rObjectToDevice,
            i_rPolygon,
            i_fTransparency,
            i_rLineWidth,
            i_eLineJoin,
            i_eLineCap,
            i_fMiterMinimumAngle,
            bPixelSnapHairline);
    }

    return bRet;
}

diff --git a/vcl/source/outdev/line.cxx b/vcl/source/outdev/line.cxx
index 8c856a8..fe16d67 100644
--- a/vcl/source/outdev/line.cxx
+++ b/vcl/source/outdev/line.cxx
@@ -132,18 +132,21 @@
        aB2DPolyLine.append(basegfx::B2DPoint(rEndPt.X(), rEndPt.Y()));
        aB2DPolyLine.transform( aTransform );

        if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
        {
            aB2DPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine);
        }
        const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
        // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
        // {
        //     aB2DPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine);
        // }

        if( mpGraphics->DrawPolyLine(
            basegfx::B2DHomMatrix(),
            aB2DPolyLine,
            0.0,
            aB2DLineWidth,
            basegfx::B2DLineJoin::NONE,
            css::drawing::LineCap_BUTT,
            basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
            bPixelSnapHairline,
            this))
        {
            return;
@@ -242,24 +245,30 @@
        for(sal_uInt32 a(0); a < aLinePolyPolygon.count(); a++)
        {
            const basegfx::B2DPolygon aCandidate(aLinePolyPolygon.getB2DPolygon(a));
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
            bool bDone(false);

            if(bTryAA)
            {
                bDone = mpGraphics->DrawPolyLine(
                    basegfx::B2DHomMatrix(),
                    aCandidate,
                    0.0,
                    basegfx::B2DVector(1.0,1.0),
                    basegfx::B2DLineJoin::NONE,
                    css::drawing::LineCap_BUTT,
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                    bPixelSnapHairline,
                    this);
            }

            if(!bDone)
            {
                tools::Polygon aPolygon(aCandidate);
                mpGraphics->DrawPolyLine(aPolygon.GetSize(), reinterpret_cast<SalPoint*>(aPolygon.GetPointAry()), this);
                mpGraphics->DrawPolyLine(
                    aPolygon.GetSize(),
                    reinterpret_cast<SalPoint*>(aPolygon.GetPointAry()),
                    this);
            }
        }
    }
diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx
index bef902a..94ad52a 100644
--- a/vcl/source/outdev/polygon.cxx
+++ b/vcl/source/outdev/polygon.cxx
@@ -86,21 +86,25 @@
        if(bSuccess && IsLineColor())
        {
            const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);

            if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
            {
                aB2DPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon);
            }
            // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
            // {
            //     aB2DPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon);
            // }

            for(sal_uInt32 a(0); bSuccess && a < aB2DPolyPolygon.count(); a++)
            {
                bSuccess = mpGraphics->DrawPolyLine( aB2DPolyPolygon.getB2DPolygon(a),
                                                     0.0,
                                                     aB2DLineWidth,
                                                     basegfx::B2DLineJoin::NONE,
                                                     css::drawing::LineCap_BUTT,
                                                     basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                                                     this);
                bSuccess = mpGraphics->DrawPolyLine(
                    basegfx::B2DHomMatrix(),
                    aB2DPolyPolygon.getB2DPolygon(a),
                    0.0,
                    aB2DLineWidth,
                    basegfx::B2DLineJoin::NONE,
                    css::drawing::LineCap_BUTT,
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                    bPixelSnapHairline,
                    this);
            }
        }

@@ -199,19 +203,23 @@
        if(bSuccess && IsLineColor())
        {
            const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);

            if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
            {
                aB2DPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon);
            }
            // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
            // {
            //     aB2DPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon);
            // }

            bSuccess = mpGraphics->DrawPolyLine( aB2DPolygon,
                                                 0.0,
                                                 aB2DLineWidth,
                                                 basegfx::B2DLineJoin::NONE,
                                                 css::drawing::LineCap_BUTT,
                                                 basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                                                 this);
            bSuccess = mpGraphics->DrawPolyLine(
                basegfx::B2DHomMatrix(),
                aB2DPolygon,
                0.0,
                aB2DLineWidth,
                basegfx::B2DLineJoin::NONE,
                css::drawing::LineCap_BUTT,
                basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                bPixelSnapHairline,
                this);
        }

        if(bSuccess)
@@ -302,21 +310,25 @@
        if(bSuccess && IsLineColor())
        {
            const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);

            if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
            {
                aB2DPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon);
            }
            // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
            // {
            //     aB2DPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon);
            // }

            for(sal_uInt32 a(0);bSuccess && a < aB2DPolyPolygon.count(); a++)
            {
                bSuccess = mpGraphics->DrawPolyLine( aB2DPolyPolygon.getB2DPolygon(a),
                                                     0.0,
                                                     aB2DLineWidth,
                                                     basegfx::B2DLineJoin::NONE,
                                                     css::drawing::LineCap_BUTT,
                                                     basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                                                     this);
                bSuccess = mpGraphics->DrawPolyLine(
                    basegfx::B2DHomMatrix(),
                    aB2DPolyPolygon.getB2DPolygon(a),
                    0.0,
                    aB2DLineWidth,
                    basegfx::B2DLineJoin::NONE,
                    css::drawing::LineCap_BUTT,
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                    bPixelSnapHairline,
                    this);
            }
        }

diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx
index bd5ca14..b5c06b9 100644
--- a/vcl/source/outdev/polyline.cxx
+++ b/vcl/source/outdev/polyline.cxx
@@ -59,31 +59,38 @@
        InitLineColor();

    // use b2dpolygon drawing if possible
    if ( DrawPolyLineDirect( rPoly.getB2DPolygon() ) )
    if(DrawPolyLineDirect(
        basegfx::B2DHomMatrix(),
        rPoly.getB2DPolygon()))
    {
        basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
        const basegfx::B2DHomMatrix aTransform = ImplGetDeviceTransformation();
        const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
        return;
    }

        // transform the polygon
        aB2DPolyLine.transform( aTransform );
    const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
    const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
    const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
    const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);

        if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
        {
            aB2DPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine);
        }
    // transform the polygon - do not (!)
    // aB2DPolyLine.transform( aTransform );

        if(mpGraphics->DrawPolyLine(
            aB2DPolyLine,
            0.0,
            aB2DLineWidth,
            basegfx::B2DLineJoin::NONE,
            css::drawing::LineCap_BUTT,
            basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/,
            this))
        {
            return;
        }
    // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
    // {
    //     aB2DPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine);
    // }

    if(mpGraphics->DrawPolyLine(
        aTransform,
        aB2DPolyLine,
        0.0,
        aB2DLineWidth,
        basegfx::B2DLineJoin::NONE,
        css::drawing::LineCap_BUTT,
        basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/,
        bPixelSnapHairline,
        this))
    {
        return;
    }

    tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
@@ -175,8 +182,17 @@
        InitLineColor();

    // use b2dpolygon drawing if possible
    if ( DrawPolyLineDirect(rB2DPolygon, fLineWidth, 0.0, eLineJoin, eLineCap, fMiterMinimumAngle) )
    if(DrawPolyLineDirect(
        basegfx::B2DHomMatrix(),
        rB2DPolygon,
        fLineWidth,
        0.0,
        eLineJoin,
        eLineCap,
        fMiterMinimumAngle))
    {
        return;
    }

    // #i101491#
    // no output yet; fallback to geometry decomposition and use filled polygon paint
@@ -222,9 +238,15 @@
        // to avoid optical gaps
        for(sal_uInt32 a(0); a < aAreaPolyPolygon.count(); a++)
        {
            (void)DrawPolyLineDirect(aAreaPolyPolygon.getB2DPolygon(a), 0.0, 0.0,
                                     basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT,
                                     basegfx::deg2rad(15.0) /*default, not used*/, bTryAA);
            (void)DrawPolyLineDirect(
                basegfx::B2DHomMatrix(),
                aAreaPolyPolygon.getB2DPolygon(a),
                0.0,
                0.0,
                basegfx::B2DLineJoin::NONE,
                css::drawing::LineCap_BUTT,
                basegfx::deg2rad(15.0) /*default, not used*/,
                bTryAA);
        }
    }
    else
@@ -287,13 +309,15 @@
        mpAlphaVDev->DrawPolyLine( rPoly, rLineInfo );
}

bool OutputDevice::DrawPolyLineDirect( const basegfx::B2DPolygon& rB2DPolygon,
                                       double fLineWidth,
                                       double fTransparency,
                                       basegfx::B2DLineJoin eLineJoin,
                                       css::drawing::LineCap eLineCap,
                                       double fMiterMinimumAngle,
                                       bool bBypassAACheck)
bool OutputDevice::DrawPolyLineDirect(
    const basegfx::B2DHomMatrix& rObjectTransform,
    const basegfx::B2DPolygon& rB2DPolygon,
    double fLineWidth,
    double fTransparency,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle,
    bool bBypassAACheck)
{
    assert(!is_double_buffered_window());

@@ -322,37 +346,43 @@

    if(bTryAA)
    {
        const basegfx::B2DHomMatrix aTransform = ImplGetDeviceTransformation();
        basegfx::B2DVector aB2DLineWidth(1.0, 1.0);
        // combine rObjectTransform with WorldToDevice
        const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation() * rObjectTransform);
        const bool bLineWidthZero(basegfx::fTools::equalZero(fLineWidth));
        const basegfx::B2DVector aB2DLineWidth(bLineWidthZero ? 1.0 : fLineWidth, bLineWidthZero ? 1.0 : fLineWidth);

        // transform the line width if used
        if( fLineWidth != 0.0 )
        {
            aB2DLineWidth = aTransform * basegfx::B2DVector( fLineWidth, fLineWidth );
        }
        // if( fLineWidth != 0.0 )
        // {
        //     aB2DLineWidth = aTransform * basegfx::B2DVector( fLineWidth, fLineWidth );
        // }

        // transform the polygon
        basegfx::B2DPolygon aB2DPolygon(rB2DPolygon);
        aB2DPolygon.transform(aTransform);
        // transform the polygon - no!
        // basegfx::B2DPolygon aB2DPolygon(rB2DPolygon);
        // aB2DPolygon.transform(aTransform);

        if((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) &&
           aB2DPolygon.count() < 1000)
        {
            // #i98289#, #i101491#
            // better to remove doubles on device coordinates. Also assume from a given amount
            // of points that the single edges are not long enough to smooth
            aB2DPolygon.removeDoublePoints();
            aB2DPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon);
        }
        const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000);
        // if((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) &&
        //    aB2DPolygon.count() < 1000)
        // {
        //     // #i98289#, #i101491#
        //     // better to remove doubles on device coordinates. Also assume from a given amount
        //     // of points that the single edges are not long enough to smooth
        //     aB2DPolygon.removeDoublePoints();
        //     aB2DPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon);
        // }

        // draw the polyline
        bool bDrawSuccess = mpGraphics->DrawPolyLine( aB2DPolygon,
                                                      fTransparency,
                                                      aB2DLineWidth,
                                                      eLineJoin,
                                                      eLineCap,
                                                      fMiterMinimumAngle,
                                                      this );
        bool bDrawSuccess = mpGraphics->DrawPolyLine(
            aTransform,
            rB2DPolygon,
            fTransparency,
            aB2DLineWidth,
            eLineJoin,
            eLineCap,
            fMiterMinimumAngle,
            bPixelSnapHairline,
            this);

        if( bDrawSuccess )
        {
diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx
index 42874c7..ecd63b6 100644
--- a/vcl/source/outdev/transparent.cxx
+++ b/vcl/source/outdev/transparent.cxx
@@ -255,17 +255,22 @@
        if( bDrawnOk && IsLineColor() )
        {
            const basegfx::B2DVector aHairlineWidth(1,1);
            const sal_uInt32 nPolyCount = aB2DPolyPolygon.count();
            const sal_uInt32 nPolyCount(aB2DPolyPolygon.count());
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);

            for( sal_uInt32 nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx )
            {
                const basegfx::B2DPolygon aOnePoly = aB2DPolyPolygon.getB2DPolygon( nPolyIdx );
                const basegfx::B2DPolygon aOnePoly(aB2DPolyPolygon.getB2DPolygon(nPolyIdx));

                mpGraphics->DrawPolyLine(
                    basegfx::B2DHomMatrix(),
                    aOnePoly,
                    fTransparency,
                    aHairlineWidth,
                    basegfx::B2DLineJoin::NONE,
                    css::drawing::LineCap_BUTT,
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                    bPixelSnapHairline,
                    this );
            }
        }
@@ -358,17 +363,22 @@
            mpGraphics->SetFillColor();
            // draw the border line
            const basegfx::B2DVector aLineWidths( 1, 1 );
            const sal_uInt32 nPolyCount = aB2DPolyPolygon.count();
            const sal_uInt32 nPolyCount(aB2DPolyPolygon.count());
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);

            for( sal_uInt32 nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx )
            {
                const basegfx::B2DPolygon& rPolygon = aB2DPolyPolygon.getB2DPolygon( nPolyIdx );
                const basegfx::B2DPolygon aPolygon(aB2DPolyPolygon.getB2DPolygon(nPolyIdx));

                bDrawn = mpGraphics->DrawPolyLine(
                    rPolygon,
                    basegfx::B2DHomMatrix(),
                    aPolygon,
                    fTransparency,
                    aLineWidths,
                    basegfx::B2DLineJoin::NONE,
                    css::drawing::LineCap_BUTT,
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
                    bPixelSnapHairline,
                    this );
            }
            // prepare to restore the fill color
diff --git a/vcl/unx/generic/gdi/gdiimpl.cxx b/vcl/unx/generic/gdi/gdiimpl.cxx
index 901e9fd..c650f9d 100644
--- a/vcl/unx/generic/gdi/gdiimpl.cxx
+++ b/vcl/unx/generic/gdi/gdiimpl.cxx
@@ -1564,14 +1564,18 @@
}

bool X11SalGraphicsImpl::drawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolygon,
    double fTransparency,
    const basegfx::B2DVector& rLineWidth,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle)
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    const bool bIsHairline = (rLineWidth.getX() == rLineWidth.getY()) && (rLineWidth.getX() <= 1.2);
    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
    const basegfx::B2DVector aLineWidth(rObjectToDevice * rLineWidth);
    const bool bIsHairline((aLineWidth.getX() == aLineWidth.getY()) && (aLineWidth.getX() <= 1.2));

    // #i101491#
    if( !bIsHairline && (rPolygon.count() > 1000) )
@@ -1585,18 +1589,23 @@
        return false;
    }

    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
    basegfx::B2DPolygon aPolyLine(rPolygon);
    aPolyLine.transform(rObjectToDevice);
    if(bPixelSnapHairline) { aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); }

    // temporarily adjust brush color to pen color
    // since the line is drawn as an area-polygon
    const Color aKeepBrushColor = mnBrushColor;
    mnBrushColor = mnPenColor;

    // #i11575#desc5#b adjust B2D tessellation result to raster positions
    basegfx::B2DPolygon aPolygon = rPolygon;
    const double fHalfWidth = 0.5 * rLineWidth.getX();
    // basegfx::B2DPolygon aPolygon = rPolygon;
    const double fHalfWidth = 0.5 * aLineWidth.getX();

    // #i122456# This is probably thought to happen to align hairlines to pixel positions, so
    // it should be a 0.5 translation, not more. It will definitely go wrong with fat lines
    aPolygon.transform( basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) );
    aPolyLine.transform( basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) );

    // shortcut for hairline drawing to improve performance
    bool bDrawnOk = true;
@@ -1605,7 +1614,7 @@
        // hairlines can benefit from a simplified tessellation
        // e.g. for hairlines the linejoin style can be ignored
        basegfx::B2DTrapezoidVector aB2DTrapVector;
        basegfx::utils::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolygon, rLineWidth.getX() );
        basegfx::utils::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolyLine, aLineWidth.getX() );

        // draw tessellation result
        const int nTrapCount = aB2DTrapVector.size();
@@ -1618,21 +1627,25 @@
    }

    // get the area polygon for the line polygon
    if( (rLineWidth.getX() != rLineWidth.getY())
    && !basegfx::fTools::equalZero( rLineWidth.getY() ) )
    if( (aLineWidth.getX() != aLineWidth.getY()) && !basegfx::fTools::equalZero( aLineWidth.getY() ) )
    {
        // prepare for createAreaGeometry() with anisotropic linewidth
        aPolygon.transform( basegfx::utils::createScaleB2DHomMatrix(1.0, rLineWidth.getX() / rLineWidth.getY()));
        aPolyLine.transform( basegfx::utils::createScaleB2DHomMatrix(1.0, aLineWidth.getX() / aLineWidth.getY()));
    }

    // create the area-polygon for the line
    const basegfx::B2DPolyPolygon aAreaPolyPoly( basegfx::utils::createAreaGeometry(aPolygon, fHalfWidth, eLineJoin, eLineCap, fMiterMinimumAngle) );
    const basegfx::B2DPolyPolygon aAreaPolyPoly(
        basegfx::utils::createAreaGeometry(
            aPolyLine,
            fHalfWidth,
            eLineJoin,
            eLineCap,
            fMiterMinimumAngle));

    if( (rLineWidth.getX() != rLineWidth.getY())
    && !basegfx::fTools::equalZero( rLineWidth.getX() ) )
    if( (aLineWidth.getX() != aLineWidth.getY()) && !basegfx::fTools::equalZero( aLineWidth.getX() ) )
    {
        // postprocess createAreaGeometry() for anisotropic linewidth
        aPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(1.0, rLineWidth.getY() / rLineWidth.getX()));
        aPolyLine.transform(basegfx::utils::createScaleB2DHomMatrix(1.0, aLineWidth.getY() / aLineWidth.getX()));
    }

    // draw each area polypolygon component individually
diff --git a/vcl/unx/generic/gdi/gdiimpl.hxx b/vcl/unx/generic/gdi/gdiimpl.hxx
index 156700f..f738e1e 100644
--- a/vcl/unx/generic/gdi/gdiimpl.hxx
+++ b/vcl/unx/generic/gdi/gdiimpl.hxx
@@ -160,12 +160,14 @@
    virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;

    virtual bool drawPolyLine(
                const basegfx::B2DHomMatrix& rObjectToDevice,
                const basegfx::B2DPolygon&,
                double fTransparency,
                const basegfx::B2DVector& rLineWidths,
                basegfx::B2DLineJoin,
                css::drawing::LineCap,
                double fMiterMinimumAngle) override;
                double fMiterMinimumAngle,
                bool bPixelSnapHairline) override;

    virtual bool drawPolyLineBezier(
                sal_uInt32 nPoints,
diff --git a/vcl/unx/generic/gdi/salgdi.cxx b/vcl/unx/generic/gdi/salgdi.cxx
index 622dc22..1d6d5bf 100644
--- a/vcl/unx/generic/gdi/salgdi.cxx
+++ b/vcl/unx/generic/gdi/salgdi.cxx
@@ -696,16 +696,16 @@
#endif // ENABLE_CAIRO_CANVAS

bool X11SalGraphics::drawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolygon,
    double fTransparency,
    const basegfx::B2DVector& rLineWidth,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle)
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    const int nPointCount(rPolygon.count());

    if(nPointCount <= 0)
    if(0 == rPolygon.count())
    {
        return true;
    }
@@ -723,17 +723,41 @@
        cairo_t* cr = getCairoContext();
        clipRegion(cr);

        SvpSalGraphics::drawPolyLine(cr, mnPenColor, getAntiAliasB2DDraw(),
                                     rPolygon, fTransparency, rLineWidth,
                                     eLineJoin, eLineCap, fMiterMinimumAngle);
        // Use the now available static drawPolyLine from the Cairo-Headless-Fallback
        // that will take care of all needed stuff
        const bool bRetval(
            SvpSalGraphics::drawPolyLine(
                cr,
                nullptr,
                mnPenColor,
                getAntiAliasB2DDraw(),
                rObjectToDevice,
                rPolygon,
                fTransparency,
                rLineWidth,
                eLineJoin,
                eLineCap,
                fMiterMinimumAngle,
                bPixelSnapHairline));

        releaseCairoContext(cr);
        return true;

        if(bRetval)
        {
            return true;
        }
    }
#endif // ENABLE_CAIRO_CANVAS

    return mxImpl->drawPolyLine( rPolygon, fTransparency, rLineWidth,
            eLineJoin, eLineCap, fMiterMinimumAngle );
    return mxImpl->drawPolyLine(
        rObjectToDevice,
        rPolygon,
        fTransparency,
        rLineWidth,
        eLineJoin,
        eLineCap,
        fMiterMinimumAngle,
        bPixelSnapHairline);
}

bool X11SalGraphics::drawGradient(const tools::PolyPolygon& rPoly, const Gradient& rGradient)
diff --git a/vcl/unx/generic/print/genpspgraphics.cxx b/vcl/unx/generic/print/genpspgraphics.cxx
index a9c9483..4ee35c8 100644
--- a/vcl/unx/generic/print/genpspgraphics.cxx
+++ b/vcl/unx/generic/print/genpspgraphics.cxx
@@ -411,12 +411,14 @@
}

bool GenPspGraphics::drawPolyLine(
    const basegfx::B2DHomMatrix& /* rObjectToDevice */,
    const basegfx::B2DPolygon&,
    double /*fTransparency*/,
    const basegfx::B2DVector& /*rLineWidths*/,
    basegfx::B2DLineJoin /*eJoin*/,
    css::drawing::LineCap /*eLineCap*/,
    double /*fMiterMinimumAngle*/)
    double /*fMiterMinimumAngle*/,
    bool /* bPixelSnapHairline */)
{
    // TODO: a PS printer can draw B2DPolyLines almost directly
    return false;
diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx
index a66f1f5..bf2dcc9 100644
--- a/vcl/win/gdi/gdiimpl.cxx
+++ b/vcl/win/gdi/gdiimpl.cxx
@@ -35,6 +35,7 @@
#include <vcl/salbtype.hxx>
#include <win/salframe.h>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/utils/systemdependentdata.hxx>

#include <outdata.hxx>
#include <win/salids.hrc>
@@ -1823,28 +1824,84 @@
    return bRet;
}

basegfx::B2DPoint impPixelSnap(
    const basegfx::B2DPolygon& rPolygon,
    const basegfx::B2DHomMatrix& rObjectToDevice,
    basegfx::B2DHomMatrix& rObjectToDeviceInv,
    sal_uInt32 nIndex)
{
    const sal_uInt32 nCount(rPolygon.count());

    // get the data
    const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
    const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex));
    const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
    const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount)));

    // get the states
    const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
    const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
    const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
    const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
    const bool bSnapX(bPrevVertical || bNextVertical);
    const bool bSnapY(bPrevHorizontal || bNextHorizontal);

    if(bSnapX || bSnapY)
    {
        basegfx::B2DPoint aSnappedPoint(
            bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
            bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());

        if(rObjectToDeviceInv.isIdentity())
        {
            rObjectToDeviceInv = rObjectToDevice;
            rObjectToDeviceInv.invert();
        }

        aSnappedPoint *= rObjectToDeviceInv;

        return aSnappedPoint;
    }

    return rPolygon.getB2DPoint(nIndex);
}

void impAddB2DPolygonToGDIPlusGraphicsPathReal(
    Gdiplus::GraphicsPath& rGraphicsPath,
    const basegfx::B2DPolygon& rPolygon,
    bool bNoLineJoin)
    const basegfx::B2DHomMatrix& rObjectToDevice,
    bool bNoLineJoin,
    bool bPixelSnapHairline)
{
    sal_uInt32 nCount(rPolygon.count());

    if(nCount)
    {
        const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);
        const bool bControls(rPolygon.areControlPointsUsed());
        basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));

        if(nEdgeCount)
        {
            const bool bControls(rPolygon.areControlPointsUsed());
            basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
            basegfx::B2DHomMatrix aObjectToDeviceInv;

            if(bPixelSnapHairline)
            {
                aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0);
            }

            for(sal_uInt32 a(0); a < nEdgeCount; a++)
            {
                const sal_uInt32 nNextIndex((a + 1) % nCount);
                const basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
                basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
                const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
                const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));

                if(bPixelSnapHairline)
                {
                    aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex);
                }

                if(b1stControlPointUsed || b2ndControlPointUsed)
                {
                    basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
@@ -1914,7 +1971,12 @@
                aGraphicsPath.StartFigure();
            }

            impAddB2DPolygonToGDIPlusGraphicsPathReal(aGraphicsPath, rPolyPolygon.getB2DPolygon(a), false);
            impAddB2DPolygonToGDIPlusGraphicsPathReal(
                aGraphicsPath,
                rPolyPolygon.getB2DPolygon(a),
                basegfx::B2DHomMatrix(),
                false,
                false);

            aGraphicsPath.CloseFigure();
        }
@@ -1954,100 +2016,172 @@
     return true;
}

class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
{
private:
    Gdiplus::GraphicsPath           maGraphicsPath;
    bool                            mbPixelSnapHairline;

public:
    SystemDependentData_GraphicsPath(
        basegfx::SystemDependentDataManager& rSystemDependentDataManager);
    virtual ~SystemDependentData_GraphicsPath() override;

    Gdiplus::GraphicsPath& getGraphicsPath() { return maGraphicsPath; }

    bool getPixelSnapHairline() const { return mbPixelSnapHairline; }
    void setPixelSnapHairline(bool bNew) { mbPixelSnapHairline = bNew; }
};

SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath(
    basegfx::SystemDependentDataManager& rSystemDependentDataManager)
:   basegfx::SystemDependentData(rSystemDependentDataManager),
    maGraphicsPath(),
    mbPixelSnapHairline(false)
{
}

SystemDependentData_GraphicsPath::~SystemDependentData_GraphicsPath()
{
}

bool WinSalGraphicsImpl::drawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolygon,
    double fTransparency,
    const basegfx::B2DVector& rLineWidths,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle)
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    const sal_uInt32 nCount(rPolygon.count());

    if(mbPen && nCount)
    if(!mbPen || 0 == rPolygon.count())
    {
        Gdiplus::Graphics aGraphics(mrParent.getHDC());
        const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) ));
        const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue());
        Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(rLineWidths.getX()));
        Gdiplus::GraphicsPath aGraphicsPath(Gdiplus::FillModeAlternate);
        bool bNoLineJoin(false);
        return true;
    }

        switch(eLineJoin)
    Gdiplus::Graphics aGraphics(mrParent.getHDC());
    const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) ));
    const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue());
    Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(rLineWidths.getX()));
    bool bNoLineJoin(false);
    Gdiplus::Matrix aMatrix;

    // Set full (Object-to-Device) transformation
    aMatrix.SetElements(
        rObjectToDevice.get(0, 0),
        rObjectToDevice.get(1, 0),
        rObjectToDevice.get(0, 1),
        rObjectToDevice.get(1, 1),
        rObjectToDevice.get(0, 2),
        rObjectToDevice.get(1, 2));
    aGraphics.SetTransform(&aMatrix);

    switch(eLineJoin)
    {
        case basegfx::B2DLineJoin::NONE :
        {
            case basegfx::B2DLineJoin::NONE :
            if(basegfx::fTools::more(rLineWidths.getX(), 0.0))
            {
                if(basegfx::fTools::more(rLineWidths.getX(), 0.0))
                {
                    bNoLineJoin = true;
                }
                break;
                bNoLineJoin = true;
            }
            case basegfx::B2DLineJoin::Bevel :
            {
                aPen.SetLineJoin(Gdiplus::LineJoinBevel);
                break;
            }
            case basegfx::B2DLineJoin::Miter :
            {
                const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));

                aPen.SetMiterLimit(aMiterLimit);
                // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional
                // graphics, somewhere clipped in some distance from the edge point, dependent
                // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use
                // that instead
                aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped);
                break;
            }
            case basegfx::B2DLineJoin::Round :
            {
                aPen.SetLineJoin(Gdiplus::LineJoinRound);
                break;
            }
            break;
        }

        switch(eLineCap)
        case basegfx::B2DLineJoin::Bevel :
        {
            default: /*css::drawing::LineCap_BUTT*/
            {
                // nothing to do
                break;
            }
            case css::drawing::LineCap_ROUND:
            {
                aPen.SetStartCap(Gdiplus::LineCapRound);
                aPen.SetEndCap(Gdiplus::LineCapRound);
                break;
            }
            case css::drawing::LineCap_SQUARE:
            {
                aPen.SetStartCap(Gdiplus::LineCapSquare);
                aPen.SetEndCap(Gdiplus::LineCapSquare);
                break;
            }
            aPen.SetLineJoin(Gdiplus::LineJoinBevel);
            break;
        }
        case basegfx::B2DLineJoin::Miter :
        {
            const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));

        impAddB2DPolygonToGDIPlusGraphicsPathReal(aGraphicsPath, rPolygon, bNoLineJoin);
            aPen.SetMiterLimit(aMiterLimit);
            // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional
            // graphics, somewhere clipped in some distance from the edge point, dependent
            // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use
            // that instead
            aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped);
            break;
        }
        case basegfx::B2DLineJoin::Round :
        {
            aPen.SetLineJoin(Gdiplus::LineJoinRound);
            break;
        }
    }

    switch(eLineCap)
    {
        default: /*css::drawing::LineCap_BUTT*/
        {
            // nothing to do
            break;
        }
        case css::drawing::LineCap_ROUND:
        {
            aPen.SetStartCap(Gdiplus::LineCapRound);
            aPen.SetEndCap(Gdiplus::LineCapRound);
            break;
        }
        case css::drawing::LineCap_SQUARE:
        {
            aPen.SetStartCap(Gdiplus::LineCapSquare);
            aPen.SetEndCap(Gdiplus::LineCapSquare);
            break;
        }
    }

    // try to access buffered data
    std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
        rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>());

    if(pSystemDependentData_GraphicsPath)
    {
        // check data validity
        if(pSystemDependentData_GraphicsPath->getPixelSnapHairline() != bPixelSnapHairline)
        {
            // data invalid, forget
            pSystemDependentData_GraphicsPath.reset();
        }
    }

    if(!pSystemDependentData_GraphicsPath)
    {
        // add to buffering mechanism
        pSystemDependentData_GraphicsPath = rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
            SalGraphics::getSystemDependentDataManager());

        // fill data of buffered data
        pSystemDependentData_GraphicsPath->setPixelSnapHairline(bPixelSnapHairline);

        impAddB2DPolygonToGDIPlusGraphicsPathReal(
            pSystemDependentData_GraphicsPath->getGraphicsPath(),
            rPolygon,
            rObjectToDevice,
            bNoLineJoin,
            bPixelSnapHairline);

        if(rPolygon.isClosed() && !bNoLineJoin)
        {
            // #i101491# needed to create the correct line joins
            aGraphicsPath.CloseFigure();
            pSystemDependentData_GraphicsPath->getGraphicsPath().CloseFigure();
        }

        if(mrParent.getAntiAliasB2DDraw())
        {
            aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
        }
        else
        {
            aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
        }

        aGraphics.DrawPath(&aPen, &aGraphicsPath);
    }

    if(mrParent.getAntiAliasB2DDraw())
    {
        aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
    }
    else
    {
        aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
    }

    aGraphics.DrawPath(
        &aPen,
        &pSystemDependentData_GraphicsPath->getGraphicsPath());

    return true;
}

diff --git a/vcl/win/gdi/gdiimpl.hxx b/vcl/win/gdi/gdiimpl.hxx
index 3bd3966..83d4125 100644
--- a/vcl/win/gdi/gdiimpl.hxx
+++ b/vcl/win/gdi/gdiimpl.hxx
@@ -109,12 +109,14 @@
    virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;

    virtual bool drawPolyLine(
                const basegfx::B2DHomMatrix& rObjectToDevice,
                const basegfx::B2DPolygon&,
                double fTransparency,
                const basegfx::B2DVector& rLineWidths,
                basegfx::B2DLineJoin,
                css::drawing::LineCap,
                double fMiterMinimumAngle) override;
                double fMiterMinimumAngle,
                bool bPixelSnapHairline) override;

    virtual bool drawPolyLineBezier(
                sal_uInt32 nPoints,
diff --git a/vcl/win/gdi/salbmp.cxx b/vcl/win/gdi/salbmp.cxx
index 36bab84..fa8b011 100644
--- a/vcl/win/gdi/salbmp.cxx
+++ b/vcl/win/gdi/salbmp.cxx
@@ -64,128 +64,12 @@
    }
}

// Helper class to manage Gdiplus::Bitmap instances inside of
// WinSalBitmap

typedef ::std::map< WinSalBitmap*, sal_uInt32 > EntryMap;
static const sal_uInt32 nDefaultCycles(60);

class GdiPlusBuffer : protected cppu::BaseMutex, public Timer
{
private:
    EntryMap        maEntries;

public:
    GdiPlusBuffer( const sal_Char *pDebugName )
    :   Timer( pDebugName ),
        maEntries()
    {
        SetTimeout(1000);
        SetStatic();
    }

    ~GdiPlusBuffer() override
    {
        Stop();
    }

    void addEntry(WinSalBitmap& rEntry)
    {
        ::osl::MutexGuard aGuard(m_aMutex);
        EntryMap::iterator aFound = maEntries.find(&rEntry);

        if(aFound == maEntries.end())
        {
            if(maEntries.empty())
            {
                Start();
            }

            maEntries[&rEntry] = nDefaultCycles;
        }
    }

    void remEntry(WinSalBitmap& rEntry)
    {
        ::osl::MutexGuard aGuard(m_aMutex);
        EntryMap::iterator aFound = maEntries.find(&rEntry);

        if(aFound != maEntries.end())
        {
            maEntries.erase(aFound);

            if(maEntries.empty())
            {
                Stop();
            }
        }
    }

    void touchEntry(WinSalBitmap& rEntry)
    {
        ::osl::MutexGuard aGuard(m_aMutex);
        EntryMap::iterator aFound = maEntries.find(&rEntry);

        if(aFound != maEntries.end())
        {
            aFound->second = nDefaultCycles;
        }
    }

    // from parent Timer
    virtual void Invoke() override
    {
        ::osl::MutexGuard aGuard(m_aMutex);
        EntryMap::iterator aIter(maEntries.begin());

        while(aIter != maEntries.end())
        {
            if(aIter->second)
            {
                aIter->second--;
                ++aIter;
            }
            else
            {
                EntryMap::iterator aDelete(aIter);
                WinSalBitmap* pSource = aDelete->first;
                ++aIter;
                maEntries.erase(aDelete);

                if(maEntries.empty())
                {
                    Stop();
                }

                // delete at WinSalBitmap after entry is removed; this
                // way it would not hurt to call remEntry from there, too
                if(pSource->maGdiPlusBitmap.get())
                {
                    pSource->maGdiPlusBitmap.reset();
                    pSource->mpAssociatedAlpha = nullptr;
                }
            }
        }

        if(!maEntries.empty())
        {
            Start();
        }
    }
};

// Global instance of GdiPlusBuffer which manages Gdiplus::Bitmap
// instances

static GdiPlusBuffer aGdiPlusBuffer( "vcl::win GdiPlusBuffer aGdiPlusBuffer" );


WinSalBitmap::WinSalBitmap()
:   maSize(),
:   SalBitmap(),
    basegfx::SystemDependentDataHolder(),
    maSize(),
    mhDIB(nullptr),
    mhDDB(nullptr),
    maGdiPlusBitmap(),
    mpAssociatedAlpha(nullptr),
    mnBitCount(0)
{
}
@@ -197,11 +81,6 @@

void WinSalBitmap::Destroy()
{
    if(maGdiPlusBitmap.get())
    {
        aGdiPlusBuffer.remEntry(*this);
    }

    if( mhDIB )
        GlobalFree( mhDIB );
    else if( mhDDB )
@@ -211,45 +90,85 @@
    mnBitCount = 0;
}

class SystemDependentData_GdiPlusBitmap : public basegfx::SystemDependentData
{
private:
    std::shared_ptr<Gdiplus::Bitmap>    mpGdiPlusBitmap;
    const WinSalBitmap*                 mpAssociatedAlpha;

public:
    SystemDependentData_GdiPlusBitmap(
        basegfx::SystemDependentDataManager& rSystemDependentDataManager);
    virtual ~SystemDependentData_GdiPlusBitmap() override;

    const WinSalBitmap* getAssociatedAlpha() const { return mpAssociatedAlpha; }
    void setAssociatedAlpha(const WinSalBitmap* pNew) { mpAssociatedAlpha = pNew; }

    const std::shared_ptr<Gdiplus::Bitmap>& getGdiPlusBitmap() const { return mpGdiPlusBitmap; }
    void setGdiPlusBitmap(const std::shared_ptr<Gdiplus::Bitmap>& rNew) { mpGdiPlusBitmap = rNew; }
};

SystemDependentData_GdiPlusBitmap::SystemDependentData_GdiPlusBitmap(
    basegfx::SystemDependentDataManager& rSystemDependentDataManager)
:   basegfx::SystemDependentData(rSystemDependentDataManager),
    mpGdiPlusBitmap(),
    mpAssociatedAlpha(nullptr)
{
}

SystemDependentData_GdiPlusBitmap::~SystemDependentData_GdiPlusBitmap()
{
}

std::shared_ptr< Gdiplus::Bitmap > WinSalBitmap::ImplGetGdiPlusBitmap(const WinSalBitmap* pAlphaSource) const
{
    WinSalBitmap* pThat = const_cast< WinSalBitmap* >(this);
    std::shared_ptr< Gdiplus::Bitmap > aRetval;

    if(maGdiPlusBitmap.get() && pAlphaSource != mpAssociatedAlpha)
    {
        // #122350# if associated alpha with which the GDIPlus was constructed has changed
        // it is necessary to remove it from buffer, reset reference to it and reconstruct
        pThat->maGdiPlusBitmap.reset();
        aGdiPlusBuffer.remEntry(const_cast< WinSalBitmap& >(*this));
    }
    // try to access buffered data
    std::shared_ptr<SystemDependentData_GdiPlusBitmap> pSystemDependentData_GdiPlusBitmap(
        getSystemDependentData<SystemDependentData_GdiPlusBitmap>());

    if(maGdiPlusBitmap.get())
    if(pSystemDependentData_GdiPlusBitmap)
    {
        aGdiPlusBuffer.touchEntry(const_cast< WinSalBitmap& >(*this));
    }
    else
    {
        if(maSize.Width() > 0 && maSize.Height() > 0)
        // check data validity
        if(pSystemDependentData_GdiPlusBitmap->getAssociatedAlpha() != pAlphaSource
            || 0 == maSize.Width()
            || 0 == maSize.Height())
        {
            if(pAlphaSource)
            {
                pThat->maGdiPlusBitmap.reset(pThat->ImplCreateGdiPlusBitmap(*pAlphaSource));
                pThat->mpAssociatedAlpha = pAlphaSource;
            }
            else
            {
                pThat->maGdiPlusBitmap.reset(pThat->ImplCreateGdiPlusBitmap());
                pThat->mpAssociatedAlpha = nullptr;
            }

            if(maGdiPlusBitmap.get())
            {
                aGdiPlusBuffer.addEntry(*pThat);
            }
            // #122350# if associated alpha with which the GDIPlus was constructed has changed
            // it is necessary to remove it from buffer, reset reference to it and reconstruct
            // data invalid, forget
            pSystemDependentData_GdiPlusBitmap.reset();
        }
    }

    return maGdiPlusBitmap;
    if(pSystemDependentData_GdiPlusBitmap)
    {
        // use from buffer
        aRetval = pSystemDependentData_GdiPlusBitmap->getGdiPlusBitmap();
    }
    else if(maSize.Width() > 0 && maSize.Height() > 0)
    {
        // add to buffering mechanism
        pSystemDependentData_GdiPlusBitmap = addOrReplaceSystemDependentData<SystemDependentData_GdiPlusBitmap>(
            SalGraphics::getSystemDependentDataManager());

        // create and set data
        if(pAlphaSource)
        {
            aRetval.reset(const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap(*pAlphaSource));
            pSystemDependentData_GdiPlusBitmap->setGdiPlusBitmap(aRetval);
            pSystemDependentData_GdiPlusBitmap->setAssociatedAlpha(pAlphaSource);
        }
        else
        {
            aRetval.reset(const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap());
            pSystemDependentData_GdiPlusBitmap->setGdiPlusBitmap(aRetval);
            pSystemDependentData_GdiPlusBitmap->setAssociatedAlpha(nullptr);
        }
    }

    return aRetval;
}

Gdiplus::Bitmap* WinSalBitmap::ImplCreateGdiPlusBitmap()
diff --git a/vcl/win/gdi/salgdi_gdiplus.cxx b/vcl/win/gdi/salgdi_gdiplus.cxx
index 45e760a..1f536e1 100644
--- a/vcl/win/gdi/salgdi_gdiplus.cxx
+++ b/vcl/win/gdi/salgdi_gdiplus.cxx
@@ -32,15 +32,24 @@
}

bool WinSalGraphics::drawPolyLine(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolygon& rPolygon,
    double fTransparency,
    const basegfx::B2DVector& rLineWidths,
    basegfx::B2DLineJoin eLineJoin,
    css::drawing::LineCap eLineCap,
    double fMiterMinimumAngle)
    double fMiterMinimumAngle,
    bool bPixelSnapHairline)
{
    return mpImpl->drawPolyLine(rPolygon, fTransparency, rLineWidths,
            eLineJoin, eLineCap, fMiterMinimumAngle);
    return mpImpl->drawPolyLine(
        rObjectToDevice,
        rPolygon,
        fTransparency,
        rLineWidths,
        eLineJoin,
        eLineCap,
        fMiterMinimumAngle,
        bPixelSnapHairline);
}

bool WinSalGraphics::blendBitmap(